/*
**  IMAPFolder.m
**
**  Copyright (c) 2001, 2002
**
**  Author: Ludovic Marcotte <ludovic@Sophos.ca>
**          Anthony W. Juckel <awj@digitalgreen.com>
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**  
**  This library is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**  Lesser General Public License for more details.
**  
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/

#import <Pantomime/IMAPFolder.h>

#import <Pantomime/Connection.h>
#import <Pantomime/Constants.h>
#import <Pantomime/Flags.h>
#import <Pantomime/IMAPCacheManager.h>
#import <Pantomime/IMAPCacheObject.h>
#import <Pantomime/IMAPStore.h>
#import <Pantomime/IMAPMessage.h>
#import <Pantomime/TCPConnection.h>
#import <Pantomime/NSDataExtensions.h>

@implementation IMAPFolder

//
//
//
- (id) initWithName: (NSString *) theName
{
  [super initWithName: theName];
  
  [self setSelected: YES];
  [self setDelegate: nil];

  return self;
}


//
//
//
- (void) dealloc
{
  RELEASE(imapCacheManager);
  
  [super dealloc];
}


//
//
//
- (void) appendMessageFromRawSource: (NSData *) theData
{
  [self appendMessageFromRawSource: theData
	flags: nil];
}


//
//
//
- (void) appendMessageFromRawSource: (NSData *) theData
                              flags: (Flags *) theFlags
{
  NSString *aString, *flagsAsString;
  BOOL newMessagesHaveArrived;

  IMAPStore *aStore;
  NSData *aData;
  
  newMessagesHaveArrived = NO;

  // We always NOOP before doing anything
  [self noop];

  if ( theFlags )
    {
      flagsAsString = [self _flagsAsStringFromFlags: theFlags];
    }
  else
    {
      flagsAsString = @"";
    }
  
  // We remove any invalid headers from our message
  aData = [self _removeInvalidHeadersFromMessage: theData];

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We write our IMAP command
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ APPEND \"%@\" (%@) {%d}",
					       [aStore nextTag],  // tag
					       [self name],       // folder name
					       flagsAsString,     // flags
					       [aData length]]];  // length of message in bytes
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  if ( [aString hasPrefix: @"+"] )
    {
      // We write our Message
      [[aStore tcpConnection] writeData: aData];
      
      // We send an empty line (just \r\n)
      [[aStore tcpConnection] writeLine: @""];
      
      // We read the response from our IMAP server
      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
      
      while ( ![aString hasPrefix: [NSString stringWithFormat: @"%@ OK", [aStore lastTag]]] )
	{
	  if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
	    {
	      int aCount;
	      
	      aCount = [aStore parseExists: aString];
	      
	      if (aCount > [self count])
		{
		  newMessagesHaveArrived = YES;
		}
	    }
	  else if ( [aString hasPrefix: [NSString stringWithFormat: @"%@ NO", [aStore lastTag]]] ||
		    [aString hasPrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
	    {
	      NSException *anException;
	      
	      NSDebugLog(@"IMAPFolder: APPEND failed %@", aString);
	      
	      anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
					 reason: aString
					 userInfo: nil];
	      [anException raise];
	    }

	  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	}

      while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
							    [aStore lastTag]] ])
	{
	  if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
	    {
	      int aCount;
	      
	      aCount = [aStore parseExists: aString];
	      
	      //NSDebugLog(@"IMAPFolder: new count = %d, previous = %d", aCount, [self count]);

	      if (aCount > [self count])
		{
		  newMessagesHaveArrived = YES;
		}
	    }
	  
	  // We just read the lines until the end
	  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	}
    }
  else
    {
      NSException *anException;
      
      NSDebugLog(@"IMAPFolder: APPEND failed %@", aString);
      
      anException = [NSException exceptionWithName: @"PantomimeFolderAppendMessageException"
				 reason: aString
				 userInfo: nil];
      [anException raise];
    }

  if ( newMessagesHaveArrived )
    {
      [self _newMessagesHaveArrived];
    }
}

//
//
//
- (void) deleteMessageWithUID: (int) theUID
{
  Flags *theFlags;

  theFlags = [[Flags alloc] init];
  AUTORELEASE(theFlags);

  [theFlags add: DELETED];
  
  [self setMessageFlags: theFlags
	forUID: theUID];
}


//
// This method is used to fetch a complete message for the specified uid
// from the current folder.
//
- (NSData *) prefetchMessageWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  int msn;
  
  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We first ask for the msn of the message
  msn = [self fetchMessageMSNWithUID: theUID];

  // We ask for the body of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d RFC822",
					       [aStore nextTag], theUID, theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  //  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d RFC822",
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
						    msn]] )
    {
      NSMutableData *aMessage;
      
      aMessage = [[NSMutableData alloc] initWithData: [[aStore tcpConnection]
							    readDataOfLength:
							      [self parseMessageSegmentSizeFromString:
								      aString]] ];
      
      // We must replace all occurence of \r\n by \n in the message.
      [self _replaceCRLFInMutableData: aMessage];

      // The server response following our RFC822 can be:
      //                             )
      //                             0003 OK UID FETCH
      // OR
      //                             )
      //                             0003 OK Completed
      //
      // We just read those two lines and discard them.
      [[aStore tcpConnection] readLineBySkippingCR: YES];
      [[aStore tcpConnection] readLineBySkippingCR: YES];

      return AUTORELEASE(aMessage);
    }
  
  return nil;
}


//
// This method is used to fetch the message body for the specified uid.
//
- (NSData *) prefetchMessageBodyWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  int msn;

  // We always NOOP before doing anything
  [self noop];

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We first ask for the msn of the message
  msn = [self fetchMessageMSNWithUID: theUID];

  // We ask for the body of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d BODY[TEXT]",
					       [aStore nextTag], theUID, theUID]];

  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  // if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d BODY[TEXT]",
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
						    msn]] )
    {
      NSMutableData *aBody;

      aBody = [[NSMutableData alloc] initWithData:
					 [[aStore tcpConnection] 
					   readDataOfLength:
					     [self parseMessageSegmentSizeFromString: aString]] ];
      
      // We must replace all occurence of \r\n by \n in the body of the message.
      [self _replaceCRLFInMutableData: aBody];
      
      
      // The server response following our BODY[TEXT] can be:
      //                             )
      //                             0003 OK UID FETCH
      // OR
      //                             )
      //                             0003 OK Completed
      //
      // We just read those two lines and discard them.
      [[aStore tcpConnection] readLineBySkippingCR: YES]; // )
      [[aStore tcpConnection] readLineBySkippingCR: YES]; // 0003 OK ...
      
      return AUTORELEASE(aBody);
    }

  NSDebugLog(@"IMAPFolder: Returning nil in IMAPFolder: -prefetchMessageBodyWithUID...");
  return nil;
}

//
// This method is used to fetch a message headers for the specified uid.
//
- (NSData *) prefetchMessageHeadersWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  int msn;

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We first ask for the msn of the message
  msn = [self fetchMessageMSNWithUID: theUID];

  // We ask for the headers of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d BODY[HEADER]",
					       [aStore nextTag], theUID, theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
 
  //NSDebugLog(@"prefetchMessageHeadersWithUID aString = |%@|", aString);
  
  //
  // The response from the server is like that: * 2 FETCH (UID 873 BODY[HEADER] {1391}
  //                                            * 8 FETCH (FLAGS (\Recent \Seen) UID 9 BODY[HEADER] {2131}
  //             
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (", msn]] )
    {
      NSMutableData *aMutableData;
      
      aMutableData = [[NSMutableData alloc] init];

      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
      
      //
      // We can receive from the server: 0003 OK Completed
      //                                 0003 OK UID FETCH Completed                                  
      //                                  
      while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ OK",
							    [aStore lastTag]] ] )
	{
	  // We don't append IMAP's extra stuff like:
	  // )
	  // (empty line)
	  if ( [aString length] == 1 && [aString isEqualToString: @")"] )
	    {
	      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	    }
	  else if ([aString length] == 0)
	    {
	      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	    }
	  //else if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"* %d FETCH", msn]] )
	  //  {
	  //   aString = [aStore lineFromSocket];
	  //  }
	  else
	    {
	      // FIXME: Everything is legal here? (cString call..)
	      [aMutableData appendCString: [aString cString]];
	      [aMutableData appendCString: "\n"];
	      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	    }
	}
      
      return AUTORELEASE(aMutableData);
    }
  
  NSDebugLog(@"IMAPFolder: Returning nil in IMAPFolder: -prefetchMessageHeadersWithUID...");
  return nil;
}


//
// This method is used to cache the messages from the POP3 server
// locally (in memory).
//
- (BOOL) prefetch
{
  BOOL didTransferMessages;
  IMAPMessage *aMessage;
  Flags *theFlags;
  int i, last_uid;
  
  // For our cache
  IMAPCacheObject *anObject;

  didTransferMessages = NO;
  last_uid = 0;

  // We first init from our cache if we need too
  if ( [self imapCacheManager] )
    {
      NSArray *imapCacheObjects;
      NSDictionary *serverFlags;

      imapCacheObjects = [[self imapCacheManager] imapCacheObjects];
      
      if ( [imapCacheObjects count] > 0 ) 
	{
	  NSAutoreleasePool *pool;
	  NSMutableArray *cacheObjectsToDelete;

	  // We must refetch the flags in case they have changed
	  // This is done in bulk, for all messages in the cache
	  serverFlags = [self prefetchMessageFlagsFromUID: [[imapCacheObjects objectAtIndex: 0] uid]
			      toUID: [[imapCacheObjects lastObject] uid] ];
	  RETAIN(serverFlags);
	  
	  
	  pool = [[NSAutoreleasePool alloc] init];
	  cacheObjectsToDelete = [[NSMutableArray alloc] init];
	  
	  for (i = 0; i < [imapCacheObjects count]; i++)
	    {
	      Flags *previousFlags;

	      if ( (i % 100) == 0 )
		{
		  TEST_RELEASE(pool);
		  pool = [[NSAutoreleasePool alloc] init];
		} 
	      
	      anObject = [imapCacheObjects objectAtIndex: i]; 
	     
	      theFlags = (Flags *)[serverFlags objectForKey: [NSNumber numberWithInt: [anObject uid]]];
	      
	      // If serverFlags does not contain theFlags for a particular message UID,
	      // that message must have been deleted from the server. We just go
	      // at the end of the loop (ie., we don't initialize a new message)
	      if ( theFlags == nil )
		{
		  [cacheObjectsToDelete addObject: anObject];
		  continue;
		}

	      aMessage = (IMAPMessage *)[anObject message];
	      
	      [aMessage setInitialized: NO];
	      [aMessage setFolder: self];
	      [aMessage setMessageNumber: [anObject uid]];

	      // FIXME - See the RFC to know what to do about cache coherency for deleted mails
	      // We now set our new flags
	      previousFlags = [aMessage flags];
	      RETAIN(previousFlags);

	      [aMessage setFlags: theFlags];

	      // We verify we must add any extra flag we had before
	      if ( [previousFlags contain: DELETED] )
		{
		  [[aMessage flags] add: DELETED];
		}

	      if ( [previousFlags contain: TRANSFERRED] )
		{
		  [[aMessage flags] add: TRANSFERRED];
		}
	      
	      // FIXME - That one should come from the IMAP server, not
	      //         from the cache but, it is NOT saved right now
	      //         on the IMAP server.
	      if ( [previousFlags contain: ANSWERED] )
		{
		  [[aMessage flags] add: ANSWERED];
		}
	      

	      RELEASE(previousFlags);
	      
	      [self appendMessage: aMessage];

	      // We set the last UID
	      last_uid = [anObject uid];
	    
	    } // for (...)

	  // Let's remove from our cache the messages that have been deleted from
	  // the IMAP server
	  for (i = 0; i < [cacheObjectsToDelete count]; i++)
	    {
	      [[self imapCacheManager] removeObject: [cacheObjectsToDelete objectAtIndex: i]];
	    }
	  RELEASE(cacheObjectsToDelete);

	  RELEASE(pool);

	  RELEASE(serverFlags);
	}
    } 

  didTransferMessages = [self prefetchNewMessagesStartingAtUID: (last_uid + 1)];
  NSDebugLog(@"IMAPFolder: Done prefetching messages");
  return didTransferMessages;
}

//
//
//
- (BOOL) prefetchNewMessagesStartingAtUID: (int) theUID
{
  BOOL didTransferMessages;
  IMAPMessage *aMessage;
  IMAPStore *aStore;
  Flags *theFlags;
  
  NSString *aString;
  NSMutableData *aHeader;
  int uid, size, msn;

  // For our cache
  IMAPCacheObject *anObject;

  didTransferMessages = NO;
  // Request summary information about all messages
  aStore = (IMAPStore *)[self store];

  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:* (FLAGS RFC822.SIZE BODY.PEEK[HEADER])", 
					       [aStore nextTag], theUID] ];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];

  while ( [aString hasCaseInsensitivePrefix: @"* "] )
    {
      NSAutoreleasePool *pool;
     
      pool = [[NSAutoreleasePool alloc] init];

      // First, we read the MSN;
      msn = [self parseMessageMSNFromString: [aString substringFromIndex: 2]];
      
      // Now, check if the UID is included on the first line
      uid = [self parseMessageUIDFromString: aString];
      
      // Then, we read the flags;
      theFlags = [self parseMessageFlagsFromString: aString];
      
      // Then, we read the size;      
      size = [self parseMessageSizeFromString: aString];
      
      // Then, we read the header;
      aHeader = [[NSMutableData alloc] initWithData:
					   [[aStore tcpConnection] readDataOfLength:
								     [self parseMessageSegmentSizeFromString: aString]] ];
      
      // We must replace all occurence of \r\n by \n in the headers of the message.
      [self _replaceCRLFInMutableData: aHeader];

      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
      
      // Finally, we may need to read the UID (which may be at the end of the string);
      if ( !uid )
	{
	  uid = [self parseMessageUIDFromString: aString];
	}
      
      // Most IMAP server will *always* return the LAST UID even if we are prefetching *from*
      // that UID. We must just skip it in case that happens.
      if ( uid > (theUID - 1) )
	{
	  // The string read is valid, we can now add the message to our folder;
	  aMessage = [[IMAPMessage alloc] initWithHeadersFromData: aHeader];

	  // We set some initial properties to our message;
	  [aMessage setInitialized: NO];
	  [aMessage setFolder: self];
	  [aMessage setMessageNumber: uid];
	  [aMessage setFlags: theFlags];
	  [aMessage setSize: size];
	  
	  // We create our cached object;
	  anObject = [[IMAPCacheObject alloc] initWithUID: uid
					      message: aMessage];
	  
	  // NSDebugLog(@"IMAPFolder: Adding new object with UID: %d", uid);
	  [self appendMessage: aMessage];
	  RELEASE(aMessage);
	  
	  didTransferMessages = YES;

	  // We add our new entry to our cache;
	  if ( [self imapCacheManager] )
	    {
	      [[self imapCacheManager] addObject: anObject];
	    }
	  
	  RELEASE(anObject);
	}

      RELEASE(pool);

      // Finally, we read the next line from the IMAP server;
      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
      
    } /* while */
 
  // We synchronize our cache
  if ( [self imapCacheManager] )
    {
      [[self imapCacheManager] synchronize];
    }

  return didTransferMessages;
}

//
//
//
- (NSDictionary *) prefetchMessageFlagsFromUID: (int) startUID
					 toUID: (int) endUID
{
  NSMutableDictionary *serverFlags;
  NSAutoreleasePool *pool;
  IMAPStore *aStore;
  Flags *theFlags;
  NSString *aString;
  int uid;

  serverFlags = [NSMutableDictionary dictionaryWithCapacity: 100];

  aStore = (IMAPStore *)[self store];

  // We send our IMAP command
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d (FLAGS)", [aStore nextTag], startUID, endUID]];
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];

  pool = [[NSAutoreleasePool alloc] init];
  
  while ( [aString hasCaseInsensitivePrefix: @"* "] )
    {
      uid = [self parseMessageUIDFromString: aString];
      theFlags = [self parseMessageFlagsFromString: aString];
      
      [serverFlags setObject: theFlags
		   forKey: [NSNumber numberWithInt:uid]];
  
      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
    }
  
  RELEASE(pool);
  
  return serverFlags;
}


//
// This method simply close the selected mailbox (ie. folder)
//
- (void) close
{
  IMAPStore *aStore;
  NSString *aString;

  if ( ![self selected])
    {
      return;
    }

  // We sync our cache manager if we have one
  if ( [self imapCacheManager] )
    {
      NSDebugLog(@"IMAPFolder: Synchronizing the IMAP cache manager...");
      [[self imapCacheManager] synchronize];
    }

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We ask for the headers of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat:@"%@ CLOSE", [aStore nextTag]]];

  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  //
  // The response from the server can be: 0004 OK Completed
  //                                      0004 OK CLOSE completed
  //
  if (! [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
						     [aStore lastTag]] ])
    {
      NSDebugLog(@"IMAPFolder: An error occured while closing the current folder.");
    }

  // We remove our current folder from the list of opened folders in the store
  [aStore removeFolderFromOpenedFolders: self];
}


//
// This method returns all messages that have the flag 'deleted'
// All the returned message ARE IN RAW SOURCE.
//
// This method DOES NOT return a message flagged as TRANSFERRED
//
- (NSArray *) expunge: (BOOL) returnDeletedMessages
{
  NSMutableArray *aMutableArray;
  NSString *aString;
  IMAPStore *aStore;
  int i;

  aMutableArray = [[NSMutableArray alloc] init];

  for (i = [allMessages count] - 1; i >= 0; i--)
    {
      IMAPMessage *aMessage;
      
      aMessage = (IMAPMessage *)[allMessages objectAtIndex: i];

      // If the message has been flagged as DELETED
      if ( [[aMessage flags] contain: DELETED] || 
	   [[aMessage flags] contain: TRANSFERRED] )
	{
	  // We add it to our array of returned message
	  if ( [[aMessage flags] contain: DELETED] &&
	       returnDeletedMessages )
	    {
	      NSData *aData = [aMessage rawSource];
	      [aMutableArray addObject: aData];
	    }
	  
	  [self deleteMessageWithUID: [aMessage messageNumber] ];
	  [super removeMessage: aMessage];
	  
	  // We remove its entry in our cache
	  if ( [self imapCacheManager] )
	    {
	      IMAPCacheObject *anObject;
	  
	      anObject = [[self imapCacheManager] findIMAPCacheObject:  [aMessage messageNumber]];
	      [[self imapCacheManager] removeObject: anObject];
	    }
	}
    }

  // We sync our cache manager if we have one
  if ( [self imapCacheManager] )
    {
      NSDebugLog(@"IMAPFolder: Synchronizing the IMAP cache manager...");
      [[self imapCacheManager] synchronize];
    }

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We finally call EXPUNGE on our IMAP server
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ EXPUNGE", 
					       [aStore nextTag]] ];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ OK",
							[aStore lastTag]] ] )
    {
      // If we got an error, we stop.
      if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ NO", [aStore lastTag]]] ||
	   [aString hasCaseInsensitivePrefix: [NSString stringWithFormat:@"%@ NO", [aStore lastTag]]] )
	{
	  NSDebugLog(@"IMAPFolder: Error occured in -expunge");
	  return [NSArray array];
	}
      
      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
    }

  return AUTORELEASE(aMutableArray);
}


//
//
//
- (Flags *) fetchMessageFlagsWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  Flags *theFlags;
  int msn;

  // We first ask for the msn of the message
  msn = [self fetchMessageMSNWithUID: theUID];
  
  // We initialize our Flags
  theFlags = [[Flags alloc] init];

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];


  // We ask for the body of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d FLAGS",
					       [aStore nextTag], theUID, theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  //
  // The server response is like: * 1 FETCH (UID 872 FLAGS (\Seen))
  //                              * 1 FETCH (FLAGS (\Seen) UID 1)                        
  //
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (", msn]] )
    {
      NSRange aRange;
      
      // We check if the message has the Seen or Recent flag
      aRange = [aString rangeOfString: @"\\Seen" options: NSCaseInsensitiveSearch];
      
      if ( aRange.length > 0 )
	{
	  [theFlags add: SEEN];
	}
      else
	{
	  [theFlags add: RECENT];
	}
      
      // We check if the message has the Deleted flag
      aRange = [aString rangeOfString: @"\\Deleted" options: NSCaseInsensitiveSearch];
      
      if ( aRange.length > 0 )
	{
	  [theFlags add: DELETED];
	}
      
      // We check if the message has the Answered flag
      aRange = [aString rangeOfString: @"\\Answered" options: NSCaseInsensitiveSearch];
      
      if ( aRange.length > 0 )
	{
	  [theFlags add: ANSWERED];
	}
      
      // We finally read the extra line on our socket and we discard it.
      [[aStore tcpConnection] readLineBySkippingCR: YES];
    }

  return AUTORELEASE(theFlags);
}


//
//
//
- (int) fetchMessageSizeWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  int size, msn;

  // We first ask for the msn of the message
  msn = [self fetchMessageMSNWithUID: theUID];

  size = 0;

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We ask for the body of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d RFC822.SIZE",
					       [aStore nextTag], theUID, theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];

  // The server response is like: * 1 FETCH (UID 872 RFC822.SIZE 4222)
  // if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (UID %d RFC822.SIZE",
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"* %d FETCH (",
						    msn]] )
    {
      NSRange aRange;

      aRange = [aString rangeOfString: @"RFC822.SIZE"];
      
      if (aRange.length > 0)
	{
	  int mark;
	  
	  mark = aRange.location + aRange.length + 1;
	  aString = [aString substringWithRange: NSMakeRange(mark, [aString length] - mark - 1)];
	  size = [aString intValue];
	}
      
      // We finally read the extra line on our socket and we discard it.
      [[aStore tcpConnection] readLineBySkippingCR: YES];
    }
  
  return size;
}



//
//
//
- (int) fetchMessageMSNWithUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];


  // We ask for the UID of the message
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:%d UID",
					       [aStore nextTag], theUID, theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  //
  // The server response is like: * 46 FETCH (UID 960)
  //
  if ( [aString hasCaseInsensitivePrefix: @"* "] )
    {      
      NSRange aRange;
      
      aString = [aString substringFromIndex: 2];
      aRange = [aString rangeOfString: @" "];
      
      if (aRange.length > 0)
	{
	  aString = [aString substringWithRange: NSMakeRange(0, aRange.location)];
	}
      
      // We finally read the extra line on our socket and we discard it.
      [[aStore tcpConnection] readLineBySkippingCR: YES];
    }

  return [aString intValue];
}

//
//
//
- (Flags *) parseMessageFlagsFromString: (NSString *) aString
{
  NSRange aRange;
  Flags *theFlags;
  
  theFlags = [[Flags alloc] init];

  // We check if the message has the Seen or Recent flag
  aRange = [aString rangeOfString: @"\\Seen" 
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: SEEN];
    }
  else
    {
      [theFlags add: RECENT];
    }
  
  // We check if the message has the Deleted flag
  aRange = [aString rangeOfString: @"\\Deleted"
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: DELETED];
    }
  
  // We check if the message has the Answered flag
  aRange = [aString rangeOfString: @"\\Answered"
		    options: NSCaseInsensitiveSearch];
      
  if ( aRange.length > 0 )
    {
      [theFlags add: ANSWERED];
    }

  // We check if the message has the Flagged flag
  aRange = [aString rangeOfString: @"\\Flagged"
		    options: NSCaseInsensitiveSearch];
  
  if ( aRange.length > 0 )
    {
      [theFlags add: FLAGGED];
    }

  // We check if the message has the Draft flag
  aRange = [aString rangeOfString: @"\\Draft"
		    options: NSCaseInsensitiveSearch];
  
  if ( aRange.length > 0 )
    {
      [theFlags add: DRAFT];
    }

  return AUTORELEASE(theFlags);
}

//
//
//
- (int) parseMessageSizeFromString: (NSString *) aString
{
  NSRange aRange;

  aRange = [aString rangeOfString: @"RFC822.SIZE"];

  if (aRange.length > 0)
    {
      int mark;
      
      mark = aRange.location + aRange.length + 1;
      
      aRange = [aString rangeOfString: @" " 
			options: 0 
			range: NSMakeRange(mark, [aString length] - mark - 1)];
      
      return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
    }
  else
    {
      return 0;
    }
}

//
//
//
- (int) parseMessageMSNFromString: (NSString *) aString
{
  NSRange aRange;

  aRange = [aString rangeOfString: @" "];
 
  if (aRange.length > 0)
    {
      return [[aString substringWithRange: NSMakeRange(0, aRange.location)] intValue];
    }
  else
    {
      return 0;
    }
}

//
//
//
- (int) parseMessageSegmentSizeFromString: (NSString *) aString
{
  NSRange aRange;
  
  aRange = [aString rangeOfString: @"{"];
 
  if (aRange.length > 0)
    {
      int mark;
      
      mark = aRange.location + aRange.length;
      aRange = [aString rangeOfString: @"}"
			options: 0
			range: NSMakeRange(mark, [aString length] - mark)];
      
      return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
    }
  else
    {
      return 0;
    }
}

//
//
//
- (int) parseMessageUIDFromString: (NSString *) aString
{
  NSRange aRange;

  aRange = [aString rangeOfString: @"UID"];
  
  if( aRange.length > 0 )
    {
      int mark = aRange.location + aRange.length + 1;
      aRange = [aString rangeOfString: @" "
			options: 0
			range: NSMakeRange(mark, [aString length] - mark)];
      
      if( aRange.length > 0 ) 
	{
	  return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
	} 
      else
	{
	  aRange = [aString rangeOfString: @")"
			    options: 0
			    range: NSMakeRange(mark, [aString length] - mark)];
	  
	  if( aRange.length > 0 )
	    {
	      return [[aString substringWithRange: NSMakeRange(mark, aRange.location - mark)] intValue];
	    }
	  else
	    {
	      return 0;
	    }
	}
    }
  else
    {
      return 0;
    }
}

// 
// Return an array of NSNumber
//
- (NSArray *) uncachedUIDStartingAtUID: (int) theUID
{
  NSMutableArray *aMutableArray;
  IMAPStore *aStore;
  NSString *aString;

  NSDebugLog(@"IMAPFolder: Starting search from UID = %d", theUID);

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];

  // We init our return value
  aMutableArray = [[NSMutableArray alloc] init];
  
  // We ask for the list of UIDs
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID FETCH %d:* UID", 
					       [aStore nextTag], theUID]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];

  while ( [aString hasCaseInsensitivePrefix: @"* "] )
    { 
      NSRange aRange;
      
      aRange = [aString rangeOfString: @"FETCH (UID"];
      
      if (aRange.length > 0)
	{
	  aString = [aString substringWithRange: NSMakeRange(aRange.location + aRange.length + 1, 
							     [aString length] - (aRange.location + aRange.length) - 2)];
	  
	  //NSDebugLog(@"Adding uid from string = |%@|", aString);
	  [aMutableArray addObject: [NSNumber numberWithInt: [aString intValue]]];
	}

      aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
    }

  return AUTORELEASE(aMutableArray);
}


//
//
//
- (void) setMessageFlags: (Flags *) theFlags
		  forUID: (int) theUID
{
  IMAPStore *aStore;
  NSString *aString;
  NSMutableString *flagString;
  
  if (! theFlags )
    {
      return;
    }

  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];
  
  // We create our string of flags
  flagString = [[NSMutableString alloc] init];
  
  if ( [theFlags contain: DELETED] )
    {
      [flagString appendString: @"\\Deleted"];
    }


  // Set the flag to \Deleted to the message at the specified index
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ UID STORE %d:%d +FLAGS (%@)",
					       [aStore nextTag], theUID, theUID, flagString]];
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ NO", [aStore lastTag]]] ||
       [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
    {
      NSDebugLog(@"IMAPFolder: An error occured while setting the flags of message the message with UID = %@", theUID);
    }
  else
    {
      //
      // The response can be: 0005 OK UID STORE
      //                      0003 OK Completed
      //
      while (! [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
							    [aStore lastTag]] ])
	{
	  // We just read the lines until the end
	  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	}
    }
  
  RELEASE(flagString);
}


//
//
//
- (void) noop
{
  IMAPStore *aStore;
  NSString *aString;
  BOOL newMessagesHaveArrived;

  newMessagesHaveArrived = NO;
  
  // We obtain the pointer to our store
  aStore = (IMAPStore *)[self store];
  
  // Set the flag to \Deleted to the message at the specified index
  [[aStore tcpConnection] writeLine: [NSString stringWithFormat: @"%@ NOOP", [aStore nextTag]]];;
  
  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
  
  //NSDebugLog(@"aString = |%@|", aString);

  if ( [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK", [aStore lastTag]]] ||
       [aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ BAD", [aStore lastTag]]] )
    {
      return;
    }
  else
    {
      //
      // The server response can be: 0004 OK NOOP
      //                             0004 OK Completed
      // 
      while ( ![aString hasCaseInsensitivePrefix: [NSString stringWithFormat: @"%@ OK",
							    [aStore lastTag]] ])
	{
	  if ( [aString hasCaseInsensitiveSuffix: @"EXISTS"] )
	    {
	      int aCount;
	      
	      aCount = [aStore parseExists: aString];
	      
	      //NSDebugLog(@"IMAPFolder: new count = %d, previous = %d", aCount, [self count]);

	      if (aCount > [self count])
		{
		  newMessagesHaveArrived = YES;
		}
	    }
	  
	  // We just read the lines until the end
	  aString = [[aStore tcpConnection] readLineBySkippingCR: YES];
	}
    }

  if ( newMessagesHaveArrived )
    {
      [self _newMessagesHaveArrived];
    }
}

//
//
//
- (int) uidValidity
{
  return uidValidity;
}


//
//
//
- (void) setUIDValidity: (int) theUIDValidity
{
  NSDebugLog(@"IMAPFolder: UIDVALIDITY = %d", theUIDValidity);
  uidValidity = theUIDValidity;
}


//
//
//
- (IMAPCacheManager *) imapCacheManager
{
  return imapCacheManager;
}


//
//
//
- (void) setIMAPCacheManager: (IMAPCacheManager *) theIMAPCacheManager
{
  if ( theIMAPCacheManager )
    {
      RETAIN(theIMAPCacheManager);
      RELEASE(imapCacheManager);
      imapCacheManager = theIMAPCacheManager;
    }
  else
    {
      RELEASE(imapCacheManager);
      imapCacheManager = nil;
    }
}


//
//
//
- (BOOL) selected
{
  return selected;
}

- (void) setSelected: (BOOL) aBOOL
{
  selected = aBOOL;
}

//
// This method re-implement the super method to allow
// to set various flags.
//
- (void) removeMessage: (Message *) theMessage
{
  [[theMessage flags] add: TRANSFERRED];
  [super removeMessage: theMessage];
}


//
//
//
- (id) delegate
{
  return delegate;
}

- (void) setDelegate: (id) theDelegate
{
  if ( theDelegate )
    {
      RETAIN(theDelegate);
      RELEASE(delegate);
      delegate = theDelegate;
    }
  else
    {
      DESTROY(delegate);
    }
}

@end


//
// Private methods
// 
@implementation IMAPFolder (Private)

//
//
//
- (NSData *) _removeInvalidHeadersFromMessage: (NSData *) theMessage
{
  NSMutableData *aMutableData;
  NSArray *allLines;
  int i;

  // We allocate our mutable data object
  aMutableData = [[NSMutableData alloc] initWithCapacity: [theMessage length]];
  
  // We now replace all \n by \r\n
  allLines = [theMessage componentsSeparatedByCString: "\n"];
  
  for (i = 0; i < [allLines count]; i++)
    {
      NSData *aLine;

      // We get a line...
      aLine = [allLines objectAtIndex: i];

      // We skip dumb headers
      if ( [aLine hasCPrefix: "From "] )
	{
	  continue;
	}

      [aMutableData appendData: aLine];
      [aMutableData appendCString: "\r\n"];
    }

  return AUTORELEASE(aMutableData);
}


//
//
//
- (void) _replaceCRLFInMutableData: (NSMutableData *) theMutableData
{
  unsigned char *bytes, *bi, *bo;
  int delta, i,length;
  
  bytes = [theMutableData mutableBytes];
  length = [theMutableData length];
  bi = bo = bytes;
  
  for (i = delta = 0; i < length; i++, bi++)
    {
      if ( i+1 < length && bi[0] == '\r' && bi[1] == '\n')
	{
	  i++;
	  bi++;
	  delta++;
	}
      
      *bo = *bi;
      bo++;
    }
  
  [theMutableData setLength: length-delta];
}


//
//
//
- (NSString *) _flagsAsStringFromFlags: (Flags *) theFlags
{
  NSMutableString *aMutableString;

  aMutableString = [[NSMutableString alloc] init];
  AUTORELEASE(aMutableString);

  if ( [theFlags contain: ANSWERED] )
    {
      [aMutableString appendString: @"\\Answered "];
    }

  if ( [theFlags contain: DRAFT] )
    {
      [aMutableString appendString: @"\\Draft "];
    }

  if ( [theFlags contain: FLAGGED] )
    {
      [aMutableString appendString: @"\\Flagged "];
    }

  if ( [theFlags contain: SEEN] )
    {
      [aMutableString appendString: @"\\Seen "];
    }
  
  if ( [theFlags contain: DELETED] )
    {
      [aMutableString appendString: @"\\Deleted "];
    }

  return [aMutableString stringByTrimmingWhiteSpaces];
}


//
//
//
- (void) _newMessagesHaveArrived
{
  SEL aSelector;

  int last_uid;
  
  last_uid = 0;
      
  if ( [self imapCacheManager] )
    {
      NSArray *imapCacheObjects;
      IMAPCacheObject *anObject;
      
      imapCacheObjects = [[self imapCacheManager] imapCacheObjects];
      
      anObject = [imapCacheObjects lastObject];
      
      last_uid = [anObject uid];
    }
      
  [self prefetchNewMessagesStartingAtUID: (last_uid + 1)];

  aSelector = @selector(newMessagesWereReceived:);
  
  if ( delegate && [delegate respondsToSelector: aSelector] )
    {
      [delegate performSelector: aSelector
		withObject: self];
    }
}

@end

